﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Threading;
using DynamicData;
using IniFileParser.Model;
using Microsoft.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore.Models;
using MilkTeaMachine.OfflineClient.Models;
using MilkTeaMachine.OfflineClient.Utils;
using MilkTeaMachine.OfflineClient.Utils.Constants;
using NModbus;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Spire.Xls;
using Splat;

namespace MilkTeaMachine.OfflineClient.ViewModels;

public partial class AutoRunningPressKeyViewModel : ViewModelBase, IRoutableViewModel
{
    private readonly ApplicationDbContext _db;
    private readonly DataTable _formulaDataTable;

    public AutoRunningPressKeyViewModel(IScreen hostScreen)
    {
        HostScreen = hostScreen;
        _db = Locator.Current.GetService<ApplicationDbContext>()!;
        GoAutoRunningScanOrder = ReactiveCommand.CreateFromObservable(
            () => hostScreen.Router.NavigateBack.Execute());
        GoPrePage = ReactiveCommand.Create(DoGoPrePage,
            this.WhenAnyValue(
                x => x.SkipCount,
                (skipCount) => skipCount > 0));
        GoNextPage = ReactiveCommand.Create(DoGoNextPage,
            this.WhenAnyValue(
                x => x.FormulaObservableCollectionRow4.Count,
                (row4Count) => row4Count == 5));
        AllCupTypeObservableCollection = new ObservableCollection<CupVo>(_defaultCupList);
        AllSugarTypeObservableCollection = new ObservableCollection<SugarVo>(_defaultSugarList);
        AllIceTypeObservableCollection = new ObservableCollection<IceVo>(_defaultIceList);
        SelectCupTypeCmd = ReactiveCommand.CreateFromTask(async (CupVo vo) => { await SelectCupTypeAsync(vo); });
        SelectSugarTypeCmd = ReactiveCommand.CreateFromTask(async (SugarVo vo) => { await SelectSugarTypeAsync(vo); });
        SelectIceTypeCmd = ReactiveCommand.CreateFromTask(async (IceVo vo) => { await SelectIceTypeAsync(vo); });
        DoItCmd = ReactiveCommand.CreateFromTask(
            async () => await DoItAsync(),
            this.WhenAnyValue(
                x => x.FormulaCodeStart3,
                x => x.SelectedCupType,
                x => x.SelectedIceType,
                x => x.SelectedSugarType,
                (@string, type1, type2, type3) =>
                    !string.IsNullOrEmpty(@string) && type1 != null && type2 != null && type3 != null));
        FormulaSelectedCmd =
            ReactiveCommand.CreateFromTask(async (string formulaCode) => await FormulaSelectedAsync(formulaCode));
        var workBook = new Workbook();
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Formulas", "配方.xlsx");
        workBook.LoadFromFile(path);
        _formulaDataTable = workBook.Worksheets[0].ExportDataTable();
        SetFormulaObservableCollections();
        ReturnToMainPageCmd =
            ReactiveCommand.CreateFromObservable(() =>
                HostScreen.Router.NavigateAndReset.Execute(new MainPageViewModel(HostScreen)));
    }

    private void DoGoNextPage()
    {
        var allDistinctFormula = _formulaDataTable.Rows.Cast<DataRow>()
            .DistinctBy(dr => dr["配方代码"].ToString()![..3])
            .Select(dr => new PressKeyFormulaVo
            {
                FormulaName = dr["配方名称"].ToString()!,
                FormulaCode = dr["配方代码"].ToString()![..3]
            }).ToArray();
        if (allDistinctFormula.Length == SkipCount * 20)
        {
            return;
        }

        SkipCount += 1;
        FormulaObservableCollectionRow1.Clear();
        FormulaObservableCollectionRow1.AddRange(allDistinctFormula.Skip(SkipCount * 20).Take(5));
        FormulaObservableCollectionRow2.Clear();
        FormulaObservableCollectionRow2.AddRange(allDistinctFormula.Skip(5 + SkipCount * 20).Take(5));
        FormulaObservableCollectionRow3.Clear();
        FormulaObservableCollectionRow3.AddRange(allDistinctFormula.Skip(10 + SkipCount * 20).Take(5));
        FormulaObservableCollectionRow4.Clear();
        FormulaObservableCollectionRow4.AddRange(allDistinctFormula.Skip(15 + SkipCount * 20).Take(5));
    }

    private void DoGoPrePage()
    {
        var allDistinctFormula = _formulaDataTable.Rows.Cast<DataRow>()
            .DistinctBy(dr => dr["配方代码"].ToString()![..3])
            .Select(dr => new PressKeyFormulaVo
            {
                FormulaName = dr["配方名称"].ToString()!,
                FormulaCode = dr["配方代码"].ToString()![..3]
            }).ToArray();
        SkipCount -= 1;
        FormulaObservableCollectionRow1.Clear();
        FormulaObservableCollectionRow1.AddRange(allDistinctFormula.Skip(SkipCount * 20).Take(5));
        FormulaObservableCollectionRow2.Clear();
        FormulaObservableCollectionRow2.AddRange(allDistinctFormula.Skip(5 + SkipCount * 20).Take(5));
        FormulaObservableCollectionRow3.Clear();
        FormulaObservableCollectionRow3.AddRange(allDistinctFormula.Skip(10 + SkipCount * 20).Take(5));
        FormulaObservableCollectionRow4.Clear();
        FormulaObservableCollectionRow4.AddRange(allDistinctFormula.Skip(15 + SkipCount * 20).Take(5));
    }

    [Reactive] private int SkipCount { get; set; }

    private void SetFormulaObservableCollections()
    {
        var allDistinctFormula = _formulaDataTable.Rows.Cast<DataRow>()
            .DistinctBy(dr => dr["配方代码"].ToString()![..3])
            .Select(dr => new PressKeyFormulaVo
            {
                FormulaName = dr["配方名称"].ToString()!,
                FormulaCode = dr["配方代码"].ToString()![..3]
            }).ToArray();
        FormulaObservableCollectionRow1.Clear();
        FormulaObservableCollectionRow1.AddRange(allDistinctFormula.Take(5));
        FormulaObservableCollectionRow2.Clear();
        FormulaObservableCollectionRow2.AddRange(allDistinctFormula.Skip(5).Take(5));
        FormulaObservableCollectionRow3.Clear();
        FormulaObservableCollectionRow3.AddRange(allDistinctFormula.Skip(10).Take(5));
        FormulaObservableCollectionRow4.Clear();
        FormulaObservableCollectionRow4.AddRange(allDistinctFormula.Skip(15).Take(5));
    }

    private Task FormulaSelectedAsync(string formulaCode)
    {
        FormulaCodeStart3 = formulaCode[..3];
        return Task.CompletedTask;
    }

    public AutoRunningPressKeyViewModel() : this(default!)
    {
    }

    [Reactive] private string FormulaCodeStart3 { get; set; } = string.Empty;
    public string? UrlPathSegment { get; } = nameof(AutoRunningPressKeyViewModel);
    public IScreen HostScreen { get; }
    [Reactive] private SugarTypeEnum? SelectedSugarType { get; set; }
    [Reactive] private IceTypeEnum? SelectedIceType { get; set; }
    [Reactive] private CupTypeEnum? SelectedCupType { get; set; }
    public ReactiveCommand<Unit, IRoutableViewModel> GoAutoRunningScanOrder { get; }
    public ReactiveCommand<Unit, Unit> GoPrePage { get; }
    public ObservableCollection<CupVo> AllCupTypeObservableCollection { get; }
    public ReactiveCommand<CupVo, Unit> SelectCupTypeCmd { get; }
    public ObservableCollection<SugarVo> AllSugarTypeObservableCollection { get; }
    public ReactiveCommand<SugarVo, Unit> SelectSugarTypeCmd { get; }
    public ObservableCollection<IceVo> AllIceTypeObservableCollection { get; }
    public ReactiveCommand<IceVo, Unit> SelectIceTypeCmd { get; }
}

public partial class AutoRunningPressKeyViewModel
{
    private Task SelectIceTypeAsync(IceVo ice)
    {
        if (ice.IsSelected || ice.IsTitle)
            return Task.CompletedTask;
        ProcessLastSelectedIce();
        ice.IsSelected = true;
        SetIceProperly(ice);
        var pos = AllIceTypeObservableCollection.IndexOf(ice);
        AllIceTypeObservableCollection.Remove(ice);
        AllIceTypeObservableCollection.Insert(pos, ice);
        SelectedIceType = ice.IceType;
        return Task.CompletedTask;
    }

    private void ProcessLastSelectedIce()
    {
        var lastSelectedItem = AllIceTypeObservableCollection
            .First(vo => vo.IsSelected);
        lastSelectedItem.IsSelected = false;
        var lastSelectedItemIndex = AllIceTypeObservableCollection.IndexOf(lastSelectedItem);
        SetIceProperly(lastSelectedItem);
        AllIceTypeObservableCollection.Remove(lastSelectedItem);
        AllIceTypeObservableCollection.Insert(lastSelectedItemIndex, lastSelectedItem);
    }

    private static void SetIceProperly(IceVo vo)
    {
        if (vo.IsSelected)
            _ = vo.IceType switch
            {
                IceTypeEnum.无冰 when vo.IsTitle => vo.Image = BitmapConstants.IceActive,
                IceTypeEnum.去冰 => vo.Image = BitmapConstants.StripIceActive,
                IceTypeEnum.少冰 => vo.Image = BitmapConstants.LittleIceActive,
                IceTypeEnum.正常 => vo.Image = BitmapConstants.NormalActive,
                IceTypeEnum.常温 => vo.Image = BitmapConstants.NormalTemperatureActive,
                IceTypeEnum.无冰 => vo.Image = BitmapConstants.NoIceActive,
                _ => throw new ArgumentOutOfRangeException()
            };
        else
            _ = vo.IceType switch
            {
                IceTypeEnum.无冰 when vo.IsTitle => vo.Image = BitmapConstants.IceInactive,
                IceTypeEnum.去冰 => vo.Image = BitmapConstants.StripIceInactive,
                IceTypeEnum.少冰 => vo.Image = BitmapConstants.LittleIceInactive,
                IceTypeEnum.正常 => vo.Image = BitmapConstants.NormalInactive,
                IceTypeEnum.常温 => vo.Image = BitmapConstants.NormalTemperatureInactive,
                IceTypeEnum.无冰 => vo.Image = BitmapConstants.NoIceInactive,
                _ => throw new ArgumentOutOfRangeException()
            };
    }

    private Task SelectSugarTypeAsync(SugarVo sugar)
    {
        if (sugar.IsSelected || sugar.IsTitle)
            return Task.CompletedTask;
        ProcessLastSelectedSugar();
        sugar.IsSelected = true;
        SetSugarProperly(sugar);
        var pos = AllSugarTypeObservableCollection.IndexOf(sugar);
        AllSugarTypeObservableCollection.Remove(sugar);
        AllSugarTypeObservableCollection.Insert(pos, sugar);
        SelectedSugarType = sugar.SugarType;
        return Task.CompletedTask;
    }

    private void ProcessLastSelectedSugar()
    {
        var lastSelectedItem = AllSugarTypeObservableCollection
            .First(vo => vo.IsSelected);
        lastSelectedItem.IsSelected = false;
        var lastSelectedItemIndex = AllSugarTypeObservableCollection.IndexOf(lastSelectedItem);
        SetSugarProperly(lastSelectedItem);
        AllSugarTypeObservableCollection.Remove(lastSelectedItem);
        AllSugarTypeObservableCollection.Insert(lastSelectedItemIndex, lastSelectedItem);
    }

    private void SetSugarProperly(SugarVo vo)
    {
        if (vo.IsSelected)
            _ = vo.SugarType switch
            {
                SugarTypeEnum.全糖 when vo.IsTitle => vo.Image = BitmapConstants.SugarActive,
                SugarTypeEnum.零糖 => vo.Image = BitmapConstants.NoSugarActive,
                SugarTypeEnum.三分 => vo.Image = BitmapConstants.ThreeSugarActive,
                SugarTypeEnum.五分 => vo.Image = BitmapConstants.FiveSugarActive,
                SugarTypeEnum.七分 => vo.Image = BitmapConstants.SevenSugarActive,
                SugarTypeEnum.全糖 => vo.Image = BitmapConstants.FullSugarActive,
                _ => throw new ArgumentOutOfRangeException()
            };
        else
            _ = vo.SugarType switch
            {
                SugarTypeEnum.全糖 when vo.IsTitle => vo.Image = BitmapConstants.SugarInactive,
                SugarTypeEnum.零糖 => vo.Image = BitmapConstants.NoSugarInactive,
                SugarTypeEnum.三分 => vo.Image = BitmapConstants.ThreeSugarInactive,
                SugarTypeEnum.五分 => vo.Image = BitmapConstants.FiveSugarInactive,
                SugarTypeEnum.七分 => vo.Image = BitmapConstants.SevenSugarInactive,
                SugarTypeEnum.全糖 => vo.Image = BitmapConstants.FullSugarInactive,
                _ => throw new ArgumentOutOfRangeException()
            };
    }

    private Task SelectCupTypeAsync(CupVo cup)
    {
        if (cup.IsSelected || cup.IsTitle)
            return Task.CompletedTask;
        ProcessLastSelectedCup();
        cup.IsSelected = true;
        SetCupProperly(cup);
        var pos = AllCupTypeObservableCollection.IndexOf(cup);
        AllCupTypeObservableCollection.Remove(cup);
        AllCupTypeObservableCollection.Insert(pos, cup);
        SelectedCupType = cup.CupType;
        return Task.CompletedTask;
    }

    private void ProcessLastSelectedCup()
    {
        var lastSelectedItem = AllCupTypeObservableCollection
            .First(vo => vo.IsSelected);
        lastSelectedItem.IsSelected = false;
        var lastSelectedItemIndex = AllCupTypeObservableCollection.IndexOf(lastSelectedItem);
        SetCupProperly(lastSelectedItem);
        AllCupTypeObservableCollection.Remove(lastSelectedItem);
        AllCupTypeObservableCollection.Insert(lastSelectedItemIndex, lastSelectedItem);
    }

    private static void SetCupProperly(CupVo vo)
    {
        if (vo.IsSelected)

            _ = vo.CupType switch
            {
                CupTypeEnum.自定 when vo.IsTitle => vo.Image = BitmapConstants.CupTypeActive,
                CupTypeEnum.自定 => vo.Image = BitmapConstants.CustomCupActive,
                CupTypeEnum.特大 => vo.Image = BitmapConstants.ExtremeBigCupActive,
                CupTypeEnum.大杯 => vo.Image = BitmapConstants.BigCupActive,
                CupTypeEnum.中杯 => vo.Image = BitmapConstants.MiddleCupActive,
                CupTypeEnum.小杯 => vo.Image = BitmapConstants.SmallCupActive,
                _ => throw new ArgumentOutOfRangeException()
            };
        else
            _ = vo.CupType switch
            {
                CupTypeEnum.自定 when vo.IsTitle => vo.Image = BitmapConstants.CupTypeInactive,
                CupTypeEnum.自定 => vo.Image = BitmapConstants.CustomCupInactive,
                CupTypeEnum.特大 => vo.Image = BitmapConstants.ExtremeBigCupInactive,
                CupTypeEnum.大杯 => vo.Image = BitmapConstants.BigCupInactive,
                CupTypeEnum.中杯 => vo.Image = BitmapConstants.MiddleCupInactive,
                CupTypeEnum.小杯 => vo.Image = BitmapConstants.SmallCupInactive,
                _ => throw new ArgumentOutOfRangeException()
            };
    }

    private Task DoItAsync()
    {
        HostScreen.Router.Navigate.Execute(new HourGlassViewModel(HostScreen, DoItAsyncDelegate));
        return Task.CompletedTask;

        async Task DoItAsyncDelegate()
        {
            var fullCode = FormulaCodeStart3 + (int)SelectedCupType! + (int)SelectedIceType! + (int)SelectedSugarType!;
            var formulaRow = _formulaDataTable.Rows.Cast<DataRow>()
                .LastOrDefault(dr => dr["配方代码"].ToString() == fullCode);
            if (formulaRow is null)
            {
                await Dispatcher.UIThread.InvokeAsync(async () =>
                {
                    await MessageBoxUtil.GetBox("没有找到该配方，请先添加该配方！").ShowWindowAsync();
                }).ConfigureAwait(true);
                return;
            }

            var excelFormulaDto = await GetFormulaAsync(formulaRow).ConfigureAwait(true);
            if (excelFormulaDto is null)
            {
                await Dispatcher.UIThread.InvokeAsync(async () =>
                {
                    await MessageBoxUtil.GetBox("没有找到该配方，请先添加该配方！").ShowWindowAsync();
                }).ConfigureAwait(true);
                return;
            }

            ToMakeFormulaData = excelFormulaDto;
            var ingredientIdList = excelFormulaDto.IngredientIdWeightDictionary.Keys;
            _ingredientIdWeightDictionary = excelFormulaDto.IngredientIdWeightDictionary;
            _ingredientDictionary = await _db.Ingredients
                .Where(ingredient => ingredientIdList.Contains(ingredient.Id))
                .ToDictionaryAsync(ingredient => ingredient.Id);
            await DoRunDeviceAsync().ConfigureAwait(true);
        }
    }

    private async Task DoRunDeviceAsync()
    {
        if (ToMakeFormulaData is null || _ingredientDictionary is null || _ingredientIdWeightDictionary is null)
        {
            await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("请先输入产品代码").ShowWindowAsync());
            return;
        }

        var parser = new IniFileParser.IniFileParser();
        var data = parser.ReadFile("Configuration/config.ini");
        var totalWeight = 0.0;
        using (var master = IModbusSerialFactory.Create())
        {
            var waitAddressArray = new List<byte>(_ingredientIdWeightDictionary.Count);
            foreach (var formulaIngredientIdAndWeight in _ingredientIdWeightDictionary)
            {
                //获取地址
                var ingredient = _ingredientDictionary[formulaIngredientIdAndWeight.Key];
                var speed = Convert.ToUInt16(ingredient.OutSpeed);
                var acceleration = Convert.ToByte(data["Machine"]["acceleration"]);
                var direction = Convert.ToByte(data["Machine"]["direction"]);
                var directionAndAcceleration = App.GetComposedUshort(direction, acceleration);
                //获取转多少圈
                var rotation = ingredient.TurnsPerGram * formulaIngredientIdAndWeight.Value;
                totalWeight += formulaIngredientIdAndWeight.Value;
                var needPulse = Convert.ToUInt32(rotation * 3200);
                var pulseHigh = (ushort)((needPulse >> 16) & 0xFFFF); // 获取高16位
                var pulseLow = (ushort)(needPulse & 0xFFFF); // 获取低16位

                //获取地址对应的地址
                var realAddress = byte.Parse(data["Address"][$"Address{ingredient.DeviceAddress}"]);
                waitAddressArray.Add(realAddress);
                await master.WriteMultipleRegistersAsync(
                    realAddress,
                    // 0x01,
                    0x00FD,
                    [directionAndAcceleration, speed, pulseHigh, pulseLow, 0]);
            }

            //等待完成
            await AutoRunningScanOrderViewModel.WaitAllFinishedAsync(waitAddressArray, master).ConfigureAwait(true);
        }

        using (var digitalControlMaster = IModbusSerialFactory.Create(9600, Parity.Even))
        {
            //额外放入冷水热水冰
            var nDeviceAddress = await AddOtherWeightIngredientAsync(data, digitalControlMaster);


            //补足重量
            if (nDeviceAddress != default)
            {
                await AddNDeviceAddressAsync(nDeviceAddress, totalWeight, data, digitalControlMaster);
            }
        }
    }

    private async Task AddNDeviceAddressAsync(int nDeviceAddress, double totalWeight, IniData data,
        IModbusSerialMaster master)
    {
        var ingredient = await _db.Ingredients.FirstAsync(i => i.DeviceAddress == nDeviceAddress);
        //获取剩余多少重量
        var restWeight = await GetCurrentWeightAsync(data) - totalWeight;
        if (restWeight < 1)
        {
            return;
        }

        var address = ingredient.IngredientName switch
        {
            "热水" => byte.Parse(data["BoolInternalAddress"]["hotWater"]),
            "冰水" => byte.Parse(data["BoolInternalAddress"]["coldWater"]),
            "冰块" => byte.Parse(data["BoolInternalAddress"]["ice"]),
            _ => throw new ArgumentOutOfRangeException()
        };
        //获取转多少秒
        var waitSeconds = Convert.ToDouble(ingredient.TurnsPerGram * restWeight);
        var modbusAddress = Convert.ToByte(data["BoolInternalAddress"]["ModbusAddress"]);
        //打开
        await master.WriteSingleCoilAsync(modbusAddress, address, true).ConfigureAwait(true);
        await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
        //关闭
        await master.WriteSingleCoilAsync(modbusAddress, address, false).ConfigureAwait(true);
    }

    private async Task<double> GetCurrentWeightAsync(IniData data)
    {
        using var master = IModbusSerialFactory.Create(9600);
        //机内称重
        var weight = await master.ReadHoldingRegistersAsync(0x01, 0x01, 2);
        if (weight is null)
        {
            _ = Dispatcher.UIThread.InvokeAsync(async () =>
            {
                await MessageBoxUtil.GetBox("读取称重失败").ShowWindowAsync().ConfigureAwait(true);
            });
            return 0.0;
        }

        var highBits = BitConverter.GetBytes(weight[0]);
        var lowBits = BitConverter.GetBytes(weight[1]);
        var weightValue = BitConverter.ToInt32([..lowBits, ..highBits]);
        weightValue = Convert.ToInt32(weightValue * Convert.ToDouble(data["Weight"]["Multiple"]));
        return weightValue;
    }

    private async Task<int> AddOtherWeightIngredientAsync(IniData data, IModbusSerialMaster master)
    {
        int nDeviceAddress = default;
        var ingredientList = await _db.Ingredients
            .Where(i => i.IngredientName == "热水" || i.IngredientName == "冰水" || i.IngredientName == "冰块").ToListAsync()
            .ConfigureAwait(true);
        //冰水、热水称重
        var modbusAddress = Convert.ToByte(data["BoolInternalAddress"]["ModbusAddress"]);
        var coldWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["coldWater"]);
        var hotWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["hotWater"]);
        var iceAddress = Convert.ToByte(data["BoolInternalAddress"]["ice"]);
        double waitSeconds;
        if (!ToMakeFormulaData!.HotWaterWeight!.Equals("N", StringComparison.OrdinalIgnoreCase))
        {
            var hotWaterIngredient = ingredientList.FirstOrDefault(ingredient => ingredient.IngredientName == "热水");
            if (hotWaterIngredient is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => MessageBoxUtil.GetBox("热水原料不存在"));
                return nDeviceAddress;
            }

            //获取转多少秒
            waitSeconds = Convert.ToDouble(hotWaterIngredient.TurnsPerGram *
                                           Convert.ToDouble(ToMakeFormulaData!.HotWaterWeight));
            //打开
            await master.WriteSingleCoilAsync(modbusAddress, hotWaterAddress, true).ConfigureAwait(true);
            await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
            //关闭
            await master.WriteSingleCoilAsync(modbusAddress, hotWaterAddress, false).ConfigureAwait(true);
            ingredientList.Remove(hotWaterIngredient);
        }

        if (!ToMakeFormulaData!.ColdWaterWeight!.Equals("N", StringComparison.OrdinalIgnoreCase))
        {
            var coldWaterIngredient = ingredientList.FirstOrDefault(ingredient => ingredient.IngredientName == "冰水");
            if (coldWaterIngredient is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => MessageBoxUtil.GetBox("冰水原料不存在"));
                return nDeviceAddress;
            }

            //获取转多少秒
            waitSeconds = Convert.ToDouble(coldWaterIngredient.TurnsPerGram *
                                           Convert.ToDouble(ToMakeFormulaData!.ColdWaterWeight));
            //打开
            await master.WriteSingleCoilAsync(modbusAddress, coldWaterAddress, true).ConfigureAwait(true);
            await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
            //关闭
            await master.WriteSingleCoilAsync(modbusAddress, coldWaterAddress, false).ConfigureAwait(true);
            ingredientList.Remove(coldWaterIngredient);
        }

        if (!ToMakeFormulaData!.IceWeight!.Equals("N", StringComparison.OrdinalIgnoreCase))
        {
            var iceIngredient = ingredientList.FirstOrDefault(ingredient => ingredient.IngredientName == "冰块");
            if (iceIngredient is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => MessageBoxUtil.GetBox("冰块原料不存在"));
                return nDeviceAddress;
            }

            //获取转多少秒
            waitSeconds = Convert.ToDouble(iceIngredient.TurnsPerGram *
                                           Convert.ToDouble(ToMakeFormulaData!.ColdWaterWeight));
            //打开
            await master.WriteSingleCoilAsync(modbusAddress, iceAddress, true).ConfigureAwait(true);
            await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
            //关闭
            await master.WriteSingleCoilAsync(modbusAddress, iceAddress, false).ConfigureAwait(true);
            ingredientList.Remove(iceIngredient);
        }

        if (ingredientList.Count != 0)
        {
            return ingredientList.First().DeviceAddress;
        }

        return default;
    }

    private Dictionary<int, Ingredient>? _ingredientDictionary;
    private Dictionary<int, double>? _ingredientIdWeightDictionary;
    private ExcelFormulaDto? ToMakeFormulaData { get; set; }

    private async Task<ExcelFormulaDto?> GetFormulaAsync(DataRow formulaRow)
    {
        var dto = new ExcelFormulaDto
        {
            FormulaName = formulaRow["配方名称"].ToString() ?? "配方名称为空",
            FormulaCode = formulaRow["配方代码"].ToString()!,
            CupType = Enum.Parse<CupTypeEnum>(formulaRow["杯型"].ToString()!),
            SugarType = Enum.Parse<SugarTypeEnum>(formulaRow["糖度"].ToString()!),
            IceType = Enum.Parse<IceTypeEnum>(formulaRow["冰块"].ToString()!),
            IngredientIdWeightDictionary = new Dictionary<int, double>(6)
        };
        for (var i = 1; i < 7; i++)
        {
            var ingredientId = formulaRow?[$"原料{i}编号"].ToString();
            var ingredientWeight = formulaRow?[$"原料{i}重量"].ToString();
            if (string.IsNullOrWhiteSpace(ingredientId) || string.IsNullOrWhiteSpace(ingredientWeight))
                continue;
            if (!double.TryParse(ingredientWeight, out var weight))
            {
                await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("配方存在非数字的重量").ShowWindowAsync());
                return null;
            }

            if (!int.TryParse(ingredientId, out var ingredientIdInt))
            {
                await Dispatcher.UIThread.InvokeAsync(() =>
                    _ = MessageBoxUtil.GetBox($"配方存在非数字的原料编号[{ingredientId}]").ShowWindowAsync());
                return null;
            }

            if (!dto.IngredientIdWeightDictionary.TryAdd(ingredientIdInt, weight))
            {
                await Dispatcher.UIThread.InvokeAsync(() =>
                    _ = MessageBoxUtil.GetBox($"配方存在重复的原料编号[{ingredientIdInt}]").ShowWindowAsync());
                return null;
            }
        }

        var ingredientIdList = dto.IngredientIdWeightDictionary.Keys;
        var ingredientList = await _db.Ingredients
            .Where(ingredient => ingredientIdList.Contains(ingredient.Id))
            .ToListAsync();
        if (ingredientList.Count != ingredientIdList.Count)
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
                _ = MessageBoxUtil.GetBox($"配方原料编号中有不存在的原料编号").ShowWindowAsync());
            return null;
        }

        if (ingredientList.Any(ingredient => ingredient.TurnsPerGram is null or < 1))
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
                _ = MessageBoxUtil.GetBox("配方存在未校准的原料").ShowWindowAsync());
            return null;
        }

        var hotWaterWeight = formulaRow!["热水重量"].ToString();
        var coldWaterWeight = formulaRow["冷水重量"].ToString();
        var iceWeight = formulaRow["冰块重量"].ToString();
        List<string?> validateList = [hotWaterWeight, coldWaterWeight, iceWeight];
        if (validateList.Count(s => s?.Equals("N", StringComparison.OrdinalIgnoreCase) is true) > 1)
        {
            await Dispatcher.UIThread.InvokeAsync(
                () => _ = MessageBoxUtil.GetBox("配方冷水、热水、冰块重量存在多个N").ShowWindowAsync());
            return null;
        }

        var notPass = validateList
            .FirstOrDefault(s =>
                s?.Equals("N", StringComparison.OrdinalIgnoreCase) is false &&
                !double.TryParse(s, out _));
        if (notPass is not null)
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
                _ = MessageBoxUtil.GetBox($"配方存在非数字的{notPass}").ShowWindowAsync());
            return null;
        }

        dto.HotWaterWeight = hotWaterWeight ?? string.Empty;
        dto.ColdWaterWeight = coldWaterWeight ?? string.Empty;
        dto.IceWeight = iceWeight ?? string.Empty;
        return dto;
    }
}

public partial class AutoRunningPressKeyViewModel
{
    private readonly List<CupVo> _defaultCupList =
    [
        new CupVo
        {
            Image = BitmapConstants.CupTypeActive,
            IsSelected = true,
            IsTitle = true,
            CupType = CupTypeEnum.自定
        },
        new CupVo
        {
            Image = BitmapConstants.ExtremeBigCupInactive,
            IsSelected = false,
            IsTitle = false,
            CupType = CupTypeEnum.特大
        },
        new CupVo
        {
            Image = BitmapConstants.BigCupInactive,
            IsSelected = false,
            IsTitle = false,
            CupType = CupTypeEnum.大杯
        },
        new CupVo
        {
            Image = BitmapConstants.MiddleCupInactive,
            IsSelected = false,
            IsTitle = false,
            CupType = CupTypeEnum.中杯
        },
        new CupVo
        {
            Image = BitmapConstants.SmallCupInactive,
            IsSelected = false,
            IsTitle = false,
            CupType = CupTypeEnum.小杯
        },
        new CupVo
        {
            Image = BitmapConstants.CustomCupInactive,
            IsSelected = false,
            IsTitle = false,
            CupType = CupTypeEnum.自定
        }
    ];

    private readonly List<SugarVo> _defaultSugarList =
    [
        new SugarVo
        {
            SugarType = SugarTypeEnum.全糖,
            Image = BitmapConstants.FullSugarActive,
            IsSelected = true,
            IsTitle = true
        },
        new SugarVo
        {
            SugarType = SugarTypeEnum.零糖,
            Image = BitmapConstants.NoSugarInactive,
            IsSelected = false,
            IsTitle = false
        },
        new SugarVo
        {
            SugarType = SugarTypeEnum.三分,
            Image = BitmapConstants.ThreeSugarInactive,
            IsSelected = false,
            IsTitle = false
        },
        new SugarVo
        {
            SugarType = SugarTypeEnum.五分,
            Image = BitmapConstants.FiveSugarInactive,
            IsSelected = false,
            IsTitle = false
        },
        new SugarVo
        {
            SugarType = SugarTypeEnum.七分,
            Image = BitmapConstants.SevenSugarInactive,
            IsSelected = false,
            IsTitle = false
        },
        new SugarVo
        {
            SugarType = SugarTypeEnum.全糖,
            Image = BitmapConstants.FullSugarInactive,
            IsSelected = false,
            IsTitle = false
        },
    ];

    private readonly List<IceVo> _defaultIceList =
    [
        new IceVo
        {
            IceType = IceTypeEnum.无冰,
            Image = BitmapConstants.IceActive,
            IsSelected = true,
            IsTitle = true
        },
        new IceVo
        {
            IceType = IceTypeEnum.去冰,
            Image = BitmapConstants.StripIceInactive,
            IsSelected = false,
            IsTitle = false
        },
        new IceVo
        {
            IceType = IceTypeEnum.少冰,
            Image = BitmapConstants.IceInactive,
            IsSelected = false,
            IsTitle = false
        },
        new IceVo
        {
            IceType = IceTypeEnum.正常,
            Image = BitmapConstants.NormalInactive,
            IsSelected = false,
            IsTitle = false
        },
        new IceVo
        {
            IceType = IceTypeEnum.常温,
            Image = BitmapConstants.NormalTemperatureInactive,
            IsSelected = false,
            IsTitle = false
        },
        new IceVo
        {
            IceType = IceTypeEnum.无冰,
            Image = BitmapConstants.NoIceInactive,
            IsSelected = false,
            IsTitle = false
        },
    ];

    public ReactiveCommand<Unit, Unit> DoItCmd { get; set; }

    public ObservableCollection<PressKeyFormulaVo> FormulaObservableCollectionRow1 { get; } = [];

    public ObservableCollection<PressKeyFormulaVo> FormulaObservableCollectionRow2 { get; } = [];

    public ObservableCollection<PressKeyFormulaVo> FormulaObservableCollectionRow3 { get; } = [];

    public ObservableCollection<PressKeyFormulaVo> FormulaObservableCollectionRow4 { get; } = [];

    public ReactiveCommand<string, Unit> FormulaSelectedCmd { get; }
    public ReactiveCommand<Unit, Unit> GoNextPage { get; }
    public ReactiveCommand<Unit, IRoutableViewModel> ReturnToMainPageCmd { get; }
}